//
//  program.h
//  rastermap
//
//  Created by Rafe Kaplan on 8/19/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#ifndef rastermap_program_h
#define rastermap_program_h

#include <rastermap.h>

#include "config.h"

class Config {
public:
    Config()
    : frame_delay(100),
      brightness(255),
      program(0),
      n_programs(1),
      program_life(30),
      fadeout(10) {
    }
    uint16_t frame_delay;           // MS to wait between frames.
    uint8_t brightness;  // 0 - 255 brightness.
    uint16_t program;               // Selected program.
    uint16_t n_programs;            // Number of programs.
    uint16_t program_life;          // How long in seconds to show selected program.  0 Means forever.
    uint8_t fadeout;               // 0 - 255 fade speed.  0 means never fade.
};

template<class Ret, class A1, class A2>
class Functor2 {
public:
  typedef Functor2<Ret, A1, A2> FunctorType;

  typedef Ret (*FnType)(A1, A2);

  static inline Ret DefaultFn(A1 a1, A2 a2) {
    return Ret();
  }

  Functor2() {
  }

  virtual Ret Fn(A1 a1, A2 a2) const {
    return Ret();
  }

  inline Ret operator()(A1 a1, A2 a2) const {
    return Fn(a1, a2);
  }
};

template<class Ret, class A1, class A2>
class Function2 : public Functor2<Ret, A1, A2> {

public:
  typedef Function2<Ret, A1, A2> FunctionType;
  typedef Functor2<Ret, A1, A2> FunctorType;
  typedef Ret (*FnType)(A1, A2);

private:
  FnType fn;

public:
  Function2() : fn(NULL) {}

  Function2(const FunctionType& functor) : fn(functor.fn) {}

  Function2(FnType fn) : fn(fn) {}

  virtual Ret Fn(A1 a1, A2 a2) const {
    if (fn) {
      return (*fn)(a1, a2);
    } else {
      return FunctorType::Fn(a1, a2);
    }
  }

  inline FunctionType& operator=(const FnType& fn) {
    this->fn = fn;
    return *this;
  }

  inline FunctionType& operator=(const FunctionType& function) {
    this->fn = function.fn;
    return *this;
  }
};

typedef class Function2<int, int, int> iFunctionII;

typedef iFunctionII::FnType iFnII;

typedef iFunctionII::FunctorType iFunctorII;

// A program is a stateful encapsulation of an Arduino program
// specifically for drawing to rasters on every loop pass.
// Program objects can be aggregated together and either run in
// parallel or run one at a time.  This allows many programs to
// reside in memory so that one may switch between them without
// having to load a new program.
class Program {
protected:
  // Since this is protected, it's always available to access
  // from a sub-class.  Remember to always use -> when accessing
  // disp.
  Raster* disp;
  unsigned long next_execution;

public:
  Program() : next_execution(0) {
  }

  virtual ~Program() {
  }

  // Reset program and set the target display raster.  Will also
  // call Init.
  virtual void Reset(Raster& disp) {
    this->disp = &disp;
    Init();
  }

  void pause(unsigned long milliseconds) {
    if (milliseconds == 0) {
      return;
    }
    unsigned long now = millis();
    next_execution = max(next_execution, now + milliseconds);
  }

  bool DoLoop(int pot) {
    if (next_execution <= millis()) {
      Loop(pot);
      return true;
    }
    return false;
  }

  // Initialize all internal state for program.  If called again,
  // reset everything nice and clean.
  virtual void Init()  {
    this->disp->Clear();
  }

  // Just like the loop function of an arduino program.
  //
  // pot is the value read from my (rafe's) potentiometer, however
  // it can serve as a generic runtime parameter.  Interpreted by
  // each program however it wants.  Typically used for refresh
  // speed.
  virtual void Loop(int pot) {
  }
};

// Encapsulation of the dipswitches installed on our Unos.  Allows
// the array of switches to be read as if it were an integer value.
class Switches {
  const short* dip_map_;
  int dip_count_;

public:
  // dip_map is an array of integers that map to digital inputs
  // on the Arduino.
  //
  // dip_count is the number of dip switches in the array.
  Switches(const short* dip_map, int dip_count)
  : dip_map_(dip_map), dip_count_(dip_count) {
  }

  void Init()  {
    for (int dip = 0; dip < dip_count_; ++dip) {
      pinMode(dip_map_[dip], INPUT);
      digitalWrite(dip_map_[dip], HIGH);
    }
  }

  // Determine if an individual dip switch is in the on position.
  bool Is(unsigned int dip) const {
    if (dip < dip_count_) {
      return digitalRead(dip_map_[dip]);
    } else {
      return false;
    }
  }

  // Represent the current dip-switch settings as an integer.
  int Number() const {
    int result = 0;
    for (int dip = 0; dip < dip_count_; ++dip) {
      int value = Is(dip);
      if (value) {
        result |= 1 << dip;
      }
    }
    return result;
  }
};

Program** Programs(Program* first, ...) {
  int count = 0;
  va_list args;

  va_start(args, first);
  for (Program* program = first;
       program != NULL;
       program = va_arg(args, Program*)) {
    ++count;
  }

  Program** programs = (Program**)malloc(sizeof(Program*) * count);
  programs[0] = first;

  count = 0;

  va_start(args, first);
  for (Program* program = first;
       program != NULL;
       program = va_arg(args, Program*)) {
    programs[count] = program;
    ++count;
  }
  programs[count] = NULL;
  return programs;
}

class MultiProgram : public Program {
  Program** programs_;
  int program_count_;

protected:
  MultiProgram()
  : program_count_(0), programs_(NULL) {
  }

  // Following switches is a list of pointers to Program objects
  // terminated by NULL.
  MultiProgram(Program** programs) {
    SetPrograms(programs);
  }

  void SetPrograms(Program** programs)  {
    program_count_ = 0;
    for (Program** p = programs; *p != NULL; ++p) {
      ++program_count_;
    }

    programs_ = programs;
  }

  Program& GetProgram(int index) {
    return *programs_[index];
  }

public:
  inline int program_count() {
    return program_count_;
  }

private:
  MultiProgram(const MultiProgram& mp) {}
};

class ProgramSelector : public MultiProgram {
  int last_program_;
  int current_program_;

public:
  ProgramSelector(Program** programs)
  : MultiProgram(programs),
    current_program_(0) {
  }

  int current_program() {
    return current_program_;
  }

  void Init() {
    last_program_ = -1;
  }

  void SetProgram(int program) {
    current_program_ = program;
  }

  Program& GetCurrentProgram() {
    return GetProgram(current_program_);
  }

  void Loop(int pot) {
    if (last_program_ != current_program_) {
      Serial.print("Switching to program ");
      Serial.print(current_program_, DEC);
      Serial.println("");
    }

    if (current_program_ < program_count()) {
      if (last_program_ != current_program_) {
        Serial.println("Program Change");
        disp->Clear();
        GetCurrentProgram().Reset(*disp);
        last_program_ = current_program_;
      }
      GetCurrentProgram().DoLoop(pot);
    } else {
      Serial.println("Unknown program");
      disp->Clear();
    }
  }
};

// Used to switch between multiple programs.
class ProgramSwitchSelector : public ProgramSelector {
  Switches* switches_;
  int last_program_;

public:
  // Following switches is a list of pointers to Program objects
  // terminated by NULL.
  ProgramSwitchSelector(Switches* switches, Program** programs)
  : ProgramSelector(programs),
    switches_(switches) {
  }

  void Init() {
    switches_->Init();
    ProgramSelector::Init();
  }

  void Loop(int pot) {
    SetProgram(switches_->Number());
    ProgramSelector::Loop(pot);
  }
};

class Composer : public MultiProgram {
public:
  Composer() : MultiProgram() {

  }

  Composer(Program** programs)
    : MultiProgram(programs) {
  }

  void Init() {
    MultiProgram::Init();
    for (int p = 0; p < program_count(); ++p) {
      Program& program = GetProgram(p);
      program.Reset(*disp);
    }
  }

  void Loop(int pot) {
    for (int p = 0; p < program_count(); ++p) {
      Program& program = GetProgram(p);
      program.DoLoop(pot);
    }
  }
};

// Double buffer program wrapper.  Used to create an in memory
// drawing space where any sub-programs will write to instead of
// the actual display.  Useful for wrapping programs that must
// read the previous frames display state because trying to read
// from a display that is missing pixels would not work out so well.
//
// After calling the sub-program's loop function will copy it's
// buffer to the actual display.
//
// It uses template parameters so that the buffer is allocated in
// program memory instead of using malloc.
template<int w, int h>
class DoubleBuffer : public Program {
  Buffer<w, h> buffer_;
  Program& program_;
public:
  DoubleBuffer(Program& program)
  : program_(program) {
  }

  void Reset(Raster& real) {
    Program::Reset(real);
    program_.Reset(buffer_);
  }

  void Loop(int pot) {
    if (program_.DoLoop(pot)) {
      //Serial.println(millis(), DEC);
      disp->Blt(buffer_, 0, 0);
    }
  }
};




class FadeBoard : public Program {
  int fade_speed;

public:
  FadeBoard(int fade_speed)
  : fade_speed(fade_speed) {
  }

  void Init() {
    Serial.println("Initialize fade board");
    disp->Clear();
  }

  void Loop(int pot) {
    for (int y = 0; y < disp->height(); ++y) {
      for (int x = 0; x < disp->width(); ++x) {
//        CHSV hsv;
//        RGBToHSV(disp->GetPixel(x, y), hsv);
//        hsv.v = max(0, ((int)hsv.v) - fade_speed);
//        disp->SetPixel(x, y, CRGB(hsv));
          CRGB pix = disp->GetPixel(x, y);
          pix.fadeToBlackBy(fade_speed);
          disp->SetPixel(x ,y, pix);
      }
    }
  }
};



// Conways game of life.  As a template.
template<int w, int h>
class Life : public FadeBoard {
  bool b1[w * h];
  bool b2[w * h];
  bool first;
  int stagnation;
  int generation;

  CRGB newColony;
  CRGB normalColony;
  CRGB sickColony;

  int sleep_for;
  int sleep;
public:
  Life(int fade_speed, int sleep_for)
  : FadeBoard(fade_speed),
    sleep_for(sleep_for) {
  }

  void Init() {
    Serial.println("Initializing life!!!");
    first = true;
    stagnation = 0;
    generation = 0;
    for (int x = 0; x < w; ++x) {
      for (int y = 0; y < h; ++y) {
        b1[x + y * w] = (random(0, 4) == 0);
      }
    }

    newColony = CRGB(RandomHSV(MAX_COLOR, MAX_COLOR));
    normalColony = CRGB(RandomHSV(MAX_COLOR, MAX_COLOR));
    sickColony = CRGB(RandomHSV(MAX_COLOR, MAX_COLOR));

    sleep = 0;
  }

  bool* Source() {
    return (first) ? b1 : b2;
  }

  bool* Dest() {
    return (first) ? b2 : b1;
  }

  bool Occupied(int x, int y) {
    if (x < 0) {
      x = w - 1;
    }
    if (y < 0) {
      y = h - 1;
    }
    x %= w;
    y %= h;

    return Source()[x + y * w];
  }

  int Neighbors(int x, int y) {
    int sum = 0;
    sum += Occupied(x + 1, y);
    sum += Occupied(x - 1, y);
    sum += Occupied(x + 1, y + 1);
    sum += Occupied(x - 1, y + 1);
    sum += Occupied(x + 1, y - 1);
    sum += Occupied(x - 1, y - 1);
    sum += Occupied(x, y + 1);
    sum += Occupied(x, y - 1);
    return sum;
  }

  void Loop(int pot) {
    FadeBoard::Loop(pot);
    if (sleep != 0) {
      sleep--;
      return;
    }

    sleep = sleep_for;
    // Draw current generation.
    for (int x = 0; x < w; ++x) {
      for (int y = 0; y < h; ++y) {
        bool alive = Source()[x + y * w];
        int neighbors = Neighbors(x, y);
        CRGB color;
        if (alive) {
          switch (neighbors) {
            case 2:
              color = normalColony;
              break;
            case 3:
              color = sickColony;
              break;
            default:
              color = newColony;
              break;
          }
          disp->SetPixel(x, y, color);
        } else {
          color = CRGB::Black;
        }
      }
    }

    // Calculate next generation.
    bool change = false;
    for (int x = 0; x < w; ++x) {
      for (int y = 0; y < h; ++y) {
        bool alive = Source()[x + y * w];
        int neighbors = Neighbors(x, y);
        bool next_gen;
        if (alive) {
          next_gen = (neighbors == 2 || neighbors == 3);
        } else {
          next_gen = (neighbors == 3);
        }
        Dest()[x + y * w] = next_gen;
        if (Dest()[x + y * w] != Source()[x + y * w])
          change = true;
      }
    }

    ++generation;
    if (!change)
      stagnation ++;
    if (stagnation == 5 || generation == 100)
      Init();
    else
      // Flip bools.
      first = !first;

    delay(pot);
  }
};

#define CYLON_LENGTH 5

// Big giant Cylon eye.  Voom-voom!
class Cylon : public Program {
  int y;
  int x[CYLON_LENGTH];
  int dir[CYLON_LENGTH];
  CRGB colors[CYLON_LENGTH];
public:
  void Init() {
    Serial.println("Init cylon");
    y = disp->height() / 2;
    x[0] = 3;
    x[1] = 2;
    x[2] = 1;
    x[3] = 0;
    x[4] = 1;

    dir[0] = 1;
    dir[1] = 1;
    dir[2] = 1;
    dir[3] = 1;
    dir[4] = -1;

    colors[0] = CRGB::Red;
    colors[1] = CRGB(64, 0, 0);
    colors[2] = CRGB(16, 0, 0);
    colors[3] = CRGB(4, 0, 0);
    colors[4] = CRGB(1, 0, 0);
  }

  void Loop(int pot) {
    for (int dot = CYLON_LENGTH - 1; dot >= 0; --dot) {
      disp->SetPixel(x[dot], y, CRGB::Black);
    }
    for (int dot = CYLON_LENGTH - 1; dot >= 0; --dot) {
      if ((dir[dot] == 1 && x[dot] == disp->width() - 1) ||
          (dir[dot] != 1 && x[dot] == 0)) {
        dir[dot] = -dir[dot];
      }
      x[dot] += dir[dot];
      disp->SetPixel(x[dot], y, colors[dot]);
    }

    delay(pot);
  }
};

int DripFunction(int x, int samples) {
  if (x < 0) {
    return 0;
  }

  if (x <= samples) {
    return MAX_COLOR;
  }

  return max(0, 16 - (log((x - samples) / samples) * 16));
}

int AAFn(int i, int samples, iFnII fn) {
  int sum = 0;
  for (int iter = 0; iter < samples; ++iter) {
    sum += fn(i + iter, samples);
  }
  return sum / samples;
}

class MemoizedAAFn {
  int count;
  int samples_;
  int* memorized;
  iFnII fn_;
public:
  MemoizedAAFn(int samples, iFnII fn)
  : samples_(samples), fn_(fn) {
  }

  void Init() {
    count = 0;
    while (AAFn(count, samples_, fn_) > 0) {
      ++count;
    }
    memorized = (int*)malloc(sizeof(int) * (count + samples_));
    for (int i = -samples_; i < count; ++i) {
      Serial.print(">>> ");
      memorized[i + samples_] = AAFn(i, samples_, fn_);
      Serial.println(memorized[i + samples_], DEC);
    }
  }

  int operator()(int i) {
    if (i < 0) return 0;
    if (i >= count) return 0;
    return memorized[i + samples_];
  }
};

class AADrip : public Program {
public:
  int from;
  int to;
  int speed;
  CRGB color;
  int super_sample_;

  int drip_y;
  int x;
  MemoizedAAFn fn;

  AADrip(int super_sample, int column)
    : x(column), fn(super_sample, &DripFunction),
      super_sample_(super_sample) {
  }

  void Init() {
    from = random(5, 10) * super_sample_;
    to = random(-10, -6) * super_sample_;
    speed = random(0, 10);
    color = RandomPixel();
    fn.Init();
  }

  void Respawn() {
    drip_y = super_sample_ * disp->height() + from;
  }

  void Loop(int pot) {
    for (int y = 0; y < disp->height(); ++y) {
      int i = y * super_sample_ - drip_y;
      int shade = fn(i);
      CRGB color(shade, shade, shade);
      disp->SetPixel(x, y, color);
    }
    drip_y -= 1;
    if (drip_y < to) {
      Respawn();
    }
    int potspeed = map(pot, 0, 1024, 1, 12);
    pause(speed * potspeed);
  }
};

class DripMachine : public Composer {
public:
  void Init() {
    Serial.println("Initializing drip machine");
    Program** programs = (Program**)malloc(
        sizeof(Program*) * (disp->width() + 1));

    if (!programs) {
      Serial.println("Ran out of memory creating the drip machine");
      while(true);
    }

    for (int i = 0; i < disp->width(); ++i) {
      Serial.print("  drip ");
      Serial.println(i, DEC);
      programs[i] = new AADrip(10, i);
      if (!programs[i]) {
        Serial.print("Ran out of memory allocating AADrip ");
        Serial.println(i, DEC);
        while(true);
      }
      programs[i]->Reset(*disp);
    }
    programs[disp->width()] = NULL;
    SetPrograms(programs);
    Serial.println("Finished initializing");
  }
};

class TestPatternAlpha : public Program {
  int x;
  int y;
public:

  virtual void Init() {
    x = 0;
    y = 0;
  }

  virtual void Loop(int pot) {
    disp->SetPixel(x, y, CRGB::Black);
    ++x;
    x = x % disp->width();
    if (x == 0) {
      ++y;
      y = y % disp->height();
    }
    disp->SetPixel(x, y, CRGB::White);
    delay(max(pot, 1));
  }
};

class TestPatternBeta : public Program {
  int i;
  int x_axis;
  int limit;
  int length;
public:
  void Init() {
    i = 0;
    x_axis = true;
    limit = disp->width();
    length = disp->height();
  }

  void Loop(int pot) {
    disp->Clear();
    const CRGB& color = RandomPixel();
    for (int pixel=0; pixel < length; ++pixel) {
      int x, y;
      if (x_axis) {
        x = i;
        y = pixel;
      } else {
        x = pixel;
        y = i;
      }
      disp->SetPixel(x, y, color);
    }
    ++i;
    if (i == limit) {
      i = 0;
      x_axis = !x_axis;
      int tmp = limit;
      limit = length;
      length = tmp;
    }
    delay(pot);
  }
};

class FullScreenPerformanceTest : public Program {

  static const int TIMING_COUNT = 16;

  bool on;
  int time_index;
  unsigned long timings[TIMING_COUNT];
  unsigned long last_time;

public:

  virtual void Init() {
    time_index = 0;
    last_time = micros();
    on = false;
  }

  virtual void Loop(int pot) {
    if (on) {
      disp->Clear(CRGB::White);
    } else {
      disp->Clear(CRGB::Black);
    }

    unsigned long current_time = micros();
    timings[time_index] = current_time - last_time;
    last_time = current_time;

    ++time_index;
    time_index %= TIMING_COUNT;
    if (time_index == 0) {
      unsigned long sum = 0;
      for (int index = 0; index < TIMING_COUNT; ++index) {
        sum += timings[index];
      }

      Serial.print("Refresh time in micros: ");
      Serial.println(sum / (unsigned long)TIMING_COUNT, DEC);
    }

    on = !on;

    if (pot) {
      delay(pot);
    }
  }
};

class Pulser : public Program {
  int color;
  int dir;
public:
  void Init() {
    color = 0;
    dir = 1;
  }

  void Loop(int pot) {
    int max_color = min(pot / 4, MAX_COLOR);
    CRGB c(0, 0, max(0, min(MAX_COLOR, color)));
    color += dir;
    if (color <= 0) {
      dir = 1;
    }
    if (color >= max_color) {
      dir = -1;
    }
    disp->Clear(c);
    delay(20);
  }
};


class Sprinker : public FadeBoard {
  int min_drops;
  int max_drops;
public:
  Sprinker(int fade_speed, int min_drops, int max_drops)
  : FadeBoard(fade_speed),
    min_drops(min_drops),
    max_drops(max_drops) {
  }

  void Loop(int pot) {
    int drops = random(min_drops, max_drops);
    if (drops > 0) {
      for (int drop = 0; drop < drops; ++drop) {
        int x = random(0, disp->width());
        int y = random(0, disp->height());
        CRGB c = RandomHSV(MAX_COLOR - 32, MAX_COLOR);
        disp->SetPixel(x, y, c);
      }
    }
    FadeBoard::Loop(pot);
  }
};


class RainDrop {
  Raster* disp;
  int min_grow_speed;
  int max_grow_speed;
  int grow_speed;
  int x;
  int y;
  int brightness;
  CRGB c;
public:

  void Init(Raster& disp,
            int min_grow_speed, int max_grow_speed) {
    this->disp = &disp;
    x = random(0, disp.width() - 1);
    y = 0;
    this->min_grow_speed = min_grow_speed;
    this->max_grow_speed = max_grow_speed;
    this->grow_speed = random(min_grow_speed, max_grow_speed);
    c = RandomHSV(MAX_COLOR - 32, MAX_COLOR);
    brightness = 0;
  }

  void Loop() {
    disp->SetPixel(x, y, CRGB(c.r, c.g, brightness));
    if (brightness == MAX_COLOR) {
      brightness = 0;
      y++;
      if (y > disp->height()) {
        Init(*disp, min_grow_speed, max_grow_speed);
      }
    } else {
      brightness = min(MAX_COLOR, brightness + grow_speed);
    }
  }
};

class Rain : public FadeBoard {
  RainDrop* drops;
  int drop_count;
  int min_grow_speed;
  int max_grow_speed;
  int slow;

public:
  Rain(int fade_speed, int drop_count,
       int min_grow_speed=8, int max_grow_speed=12)
  : FadeBoard(fade_speed),
    drops((RainDrop*)malloc(sizeof(RainDrop) * drop_count)),
    drop_count(drop_count),
    min_grow_speed(min_grow_speed),
    max_grow_speed(max_grow_speed) {
  }

  void Init() {
    disp->Clear();
    for (int drop = 0; drop < drop_count; ++drop) {
      drops[drop].Init(*disp, min_grow_speed, max_grow_speed);
    }
  }

  void Loop(int pot) {
    for (int drop = 0; drop < drop_count; ++drop) {
      drops[drop].Loop();
    }
    FadeBoard::Loop(pot);
  }
};


class CylonBar : public FadeBoard {
  int y;
  int dy;
  CRGB c;
  int grow_speed;
  int brightness;
public:
  CylonBar(int fade_speed, int grow_speed=16)
  : FadeBoard(fade_speed),
    grow_speed(grow_speed) {

  }
  void Init() {
    disp->Clear();
    c = RandomPixel();
    y = 0;
    dy = 1;
    brightness = 0;
  }

  void Loop(int pot) {
    for (int x = 0; x < disp->width(); ++x) {
      disp->SetPixel(x, y, CRGB(c.r, c.g, brightness));
    }
    FadeBoard::Loop(pot);
    if (brightness != MAX_COLOR) {
      brightness = min(MAX_COLOR, brightness + grow_speed);
      return;
    } else {
      brightness = 0;
    }
    y += dy;
    if (y == 0 || y == disp->height()) {
      dy *= -1;
      c = RandomHSV(MAX_COLOR - 32, MAX_COLOR);
    }
  }

};

class FunctionSpinner : public Program {
  iFunctionII function;
  iFunctorII* functor;

public:
  int dx;
  int dy;
  CRGB color;

  void SetFunctor(iFunctorII& functor) {
    this->functor = &functor;
  }

  void SetFunctor(iFunctionII::FnType fn) {
    function = fn;
    functor = &function;
  }

  void SetFunctor(iFunctionII function) {
    function = function;
    functor = &function;
  }

  void Init() {
    color.r = random(0, MAX_COLOR);
    color.g = color.b;
  }

  void Loop(int pot) {
    long now = millis();
    for (int y = 0; y < disp->height(); ++y) {
      for (int x = 0; x < disp->width(); ++x) {
        int value = functor->Fn(x, y + now) % MAX_COLOR;
        disp->SetPixel(x, y, CRGB(color.r, color.g, value));
      }
    }
  }
};

#endif

